using System;
using Collections = System.Collections.Generic;
using Nua;
using System.Reflection.Emit;
using Nua.Compiler;
using Microsoft.Scripting.Types;
using Microsoft.Scripting;
using Microsoft.Scripting.Internal.Ast;

namespace Nua.Ast
{
	// An assignment statement
	public sealed class Assign : Statement
	{
		public readonly Collections.IList<Var> Vars;
		public readonly Collections.IList<Expression> Exprs;

        public Assign(SourceSpan span) : base(span)
		{
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            if (Vars[0].GetType() == typeof(Slot))
            {
                Collections.List<Expression> tempExprs = new Collections.List<Expression>();
                tempExprs.Add(((Slot)Vars[0]).SlotExpr);
                foreach (Expression e in Exprs)
                    tempExprs.Add(e);
                MethodCallExpression call = new MethodCallExpression(typeof(Nua.Runtime.NuaTable).GetMethod("set_Item"), ((Slot)Vars[0]).Expression, tempExprs);
                call.Emit(cg);
            }
            else
            {
                NamedVar tempVar = (NamedVar)Vars[0];
                tempVar.EmitSet(cg, Exprs[0], Operators.None);
            }
        }

        //internal StoreLValue GenerateStorePreamble(Microsoft.Scripting.Internal.Generation.CodeGen il, Var var)
        //{
        //    NamedVar namedVar = var as NamedVar;
        //    Slot slot = var as Slot;

        //    if (namedVar != null)
        //    {
        //        return new StoreNamedVar(this.symbols, this.globalTypeBuilder, namedVar.Name);
        //    }
        //    else if (slot != null)
        //    {
        //        LocalBuilder exprLocal = il.DeclareLocal(typeof(object));
        //        LocalBuilder slotExprLocal = il.DeclareLocal(typeof(object));

        //        this.GenerateExpression(il, slot.Expr, true);
        //        il.Emit(OpCodes.Stloc, exprLocal);
        //        this.GenerateExpression(il, slot.SlotExpr, true);
        //        il.Emit(OpCodes.Stloc, slotExprLocal);

        //        return new StoreSlot(exprLocal, slotExprLocal);
        //    }
        //    else
        //    {
        //        throw new System.NotImplementedException();
        //    }
        //}

        public override void Walk(Walker walker)
        {
            if (walker.Walk(this))
            {
                if (Vars!=null)
                    foreach (Var v in Vars)
                        v.Walk(walker);
                  
                if (Exprs!=null)
                    foreach (Expression exp in Exprs)
                        exp.Walk(walker);
            }
            walker.PostWalk(this);
        }

		public Assign(SourceSpan span, Collections.IList<Var> vars, Collections.IList<Expression> exprs)
			: base(span)
		{
			this.Vars = vars;
			this.Exprs = exprs;
		}
	}

	// A function call. Name may be null for calls that do not mention a named
	// slot.
	public class FunctionCall : Expression
	{
		public readonly Expression Receiver;
		public readonly string Name; // may be null if call does not mention slot
        public Arg[] Args;

		public FunctionCall(SourceSpan span, Expression receiver, string name, Arg[] args)
		{
			this.Receiver = receiver;
			this.Name = name;
            this.Args = args;
		}

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            if (Receiver is NamedVar && ((NamedVar)Receiver).Name == "print") // special case
            {
                Collections.List<Expression> exprs = new Collections.List<Expression>();
                foreach (Arg a in Args)
                    exprs.Add(a.Expression);
                MethodCallExpression exp = new MethodCallExpression(typeof(Runtime.NuaOps).GetMethod("Print"), null, exprs);
                exp.Emit(cg);
                cg.Emit(OpCodes.Pop);
            }
            else
            {
                CallExpression exp = new CallExpression(Receiver, Args, false, false, 1, 0);
                exp.Emit(cg);
            }
        }

        public override void Walk(Walker walker)
        {
            if (walker.Walk(this))
            {
                if (Receiver!=null)
                    Receiver.Walk(walker);
                if (Args!=null)
                {
                    foreach (Arg arg in this.Args)
                        arg.Walk(walker);
                }
            }
            walker.PostWalk(this);
        }
	}

	// Wraps a function call into a statement
	public class IgnoreStmt : Statement
	{
		public readonly Expression Expression;

		public IgnoreStmt(SourceSpan span, Expression expr) : base(span)
		{
			this.Expression = expr;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            if (Expression!=null)
                Expression.Emit(cg);
        }

        public override void Walk(Walker walker)
        {
            if (walker.Walk(this))
            {
                if (Expression!=null)
                    Expression.Walk(walker);
            }
            walker.PostWalk(this);

        }

	}

	// A sequence of statements; do one, then the next
	public sealed class CompoundStmt : Statement
	{
		public readonly Statement Fst;
		public readonly Statement Snd;

		public CompoundStmt(SourceSpan span, Statement fst, Statement snd)
			: base(span)
		{
			this.Fst = fst;
			this.Snd = snd;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            Fst.Emit(cg);
            Snd.Emit(cg);
        }

        public override void Walk(Walker walker)
        {
            if (walker.Walk(this))
            {
                Fst.Walk(walker);
                Snd.Walk(walker);
            }
            walker.PostWalk(this);
        }
	}

	// While loop
	public sealed class WhileStmt : Statement
	{
		public readonly Expression Condition;
		public readonly Statement Body;

		public WhileStmt(SourceSpan span, Expression condition, Statement body) : base(span)
		{
			this.Condition = condition;
			this.Body = body;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            LoopStatement stmt = new LoopStatement(Condition, null, Body, null, Span, SourceLocation.None);
            stmt.Emit(cg);

            //Collections.Stack<Label> breakLabels = new Collections.Stack<Label>();
            //Label label1 = cg.DefineLabel();
            //Label label2 = cg.DefineLabel();
            //Label breakLabel = cg.DefineLabel();

            //cg.Emit(OpCodes.Br, label1);
            //cg.MarkLabel(label2);

            //breakLabels.Push(breakLabel);
            //Body.Emit(cg);
            //cg.MarkLabel(label1);

            //Condition.Emit(cg); 
			
            //cg.Emit(OpCodes.Ldc_I4_1);
            //cg.Emit(OpCodes.Box, typeof(bool));
            //cg.Emit(OpCodes.Call, typeof(Runtime.NuaOps).GetMethod("IfTest"));
            //cg.Emit(OpCodes.Unbox, typeof(bool));
            //cg.Emit(OpCodes.Ldobj, typeof(bool));
            //cg.Emit(OpCodes.Brtrue, label2);
            //cg.MarkLabel(breakLabel);
        }

        public override void Walk(Walker walker)
        {
            if (walker.Walk(this))
            {
                Condition.Walk(walker);
                Body.Walk(walker);
            }
            walker.PostWalk(this);
        }
	}

	// Repeat loop
	public sealed class RepeatStmt : Statement
	{
        Statement body;
        Expression condition;
        SourceLocation header;
  

		public RepeatStmt(SourceSpan span, Statement body, Expression condition, SourceLocation header)
			: base(span)
		{
            this.body = body;
            this.condition = condition;
            this.header = header;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            Label startTarget = cg.DefineLabel();
            Label breakTarget = cg.DefineLabel();
            Label continueTarget = cg.DefineLabel();

            cg.MarkLabel(startTarget);                        
            cg.PushTargets(breakTarget, continueTarget, this);
            body.Emit(cg);

            cg.MarkLabel(continueTarget);
            // TODO: Check if we need to emit position somewhere else also.
            cg.EmitPosition(Start, header);

            cg.EmitTestTrue(condition);
            cg.Emit(OpCodes.Brfalse, startTarget);

            cg.PopTargets();            
            cg.MarkLabel(breakTarget);
        }

        public override void Walk(Walker walker)
        {
            walker.PostWalk(this);
        }
	}

	// if/elseif/else statement
	public sealed class IfStmt : IfStatement
	{
		public readonly Expression Condition;
		public readonly Statement TrueBranch;
		public Statement FalseBranch;

		public IfStmt(SourceSpan span, Expression condition, Statement trueBranch, Statement falseBranch)
			: base(new IfStatementTest[] { new IfStatementTest(condition, trueBranch) }, falseBranch)
		{
			this.Condition = condition;
			this.TrueBranch = trueBranch;
			this.FalseBranch = falseBranch;
		}

        public override void Walk(Walker walker)
        {
            // todo:// figure this out
            walker.PostWalk(this);
        }

	}

	// Empty statements are used for empty blocks and the false branch of if
	// statements that don't have an else clause
	public sealed class EmptyStmt : Statement
	{
		public EmptyStmt() : base(new SourceSpan()) { }

		public EmptyStmt(SourceSpan span)
			: base(span) 
		{
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            
        }

        public override void Walk(Walker walker)
        {
            walker.PostWalk(this);
        }
	}

	// Return; may return zero or more values
    // TODO:// can't do normal return statement, because we have multiple returns
	public sealed class ReturnStmt : Statement
	{
		public readonly Collections.IList<Expression> Exprs;

		public ReturnStmt(SourceSpan span, Collections.IList<Expression> exprs)
			: base(span)
		{
			this.Exprs = exprs;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            throw new NotImplementedException();
        }

        public override void Walk(Walker walker)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }
	}

	// Breaks out of a loop
	public sealed class BreakStmt : BreakStatement
	{
		public BreakStmt(SourceSpan span) : base(span) { }
	}

	// For loop
    // Scripting doesn't give us what we need
	public sealed class ForStmt : Statement
	{
		public readonly LocalDecl Decl;
		public readonly Expression LowerBound;
		public readonly Expression UpperBound;
		public readonly Expression Step;
		public readonly Statement Body;

		public ForStmt(SourceSpan span, LocalDecl decl, Expression lowerBound, Expression upperBound, Expression step, Statement body)
			: base(span)
		{
			this.Decl = decl;
			this.LowerBound = lowerBound;
			this.UpperBound = upperBound;
			this.Step = step;
			this.Body = body;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            // scopes not done yet
            Label evaluation = cg.DefineLabel();
            Label statementStart = cg.DefineLabel();

            // lua has a rule that you assign the expression to the localdecl once only.
            VariableReference localref = Decl.Create(cg);
            ExpressionStatement assignLocal = new ExpressionStatement(new BoundAssignment(localref, LowerBound, Operators.None));
            assignLocal.Emit(cg);
            cg.Emit(OpCodes.Br, evaluation);

            // body
            cg.MarkLabel(statementStart);
            Body.Emit(cg);

            // ,exp3
            BinExpr addExp3ToLocal = new BinExpr(this.Span, new BoundExpression(localref), Operators.Add, Step);
            ExpressionStatement assignExp3ToLocal = new ExpressionStatement(new BoundAssignment(localref, addExp3ToLocal, Operators.None));
            assignExp3ToLocal.Emit(cg);

            cg.MarkLabel(evaluation);
            BinExpr evalCode = new BinExpr(this.Span, new BoundExpression(localref), Operators.GreaterThan, UpperBound);
            evalCode.Emit(cg);
            cg.EmitTestTrue();
            cg.Emit(OpCodes.Brfalse, statementStart);

        }

        public override void Walk(Walker walker)
        {
            walker.PostWalk(this);
        }
	}

	// 'foreach' loop; also written with 'for' in Lua
	public sealed class ForEachStmt : Statement
	{
		public readonly Collections.IList<LocalDecl> Decls;
		public readonly Collections.IList<Expression> Exprs;
		public readonly Statement Body;

		public ForEachStmt(SourceSpan span, Collections.IList<LocalDecl> decls, Collections.IList<Expression> exprs, Statement body)
			: base(span)
		{
			this.Decls = decls;
			this.Exprs = exprs;
			this.Body = body;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }

        public override void Walk(Walker walker)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }
	}

	// Function declaration
	public sealed class FunctionDecl : Statement
	{
		public readonly bool Local;
		public readonly Var FunctionNameExpr;
		public readonly AnonFunDecl Body;

		public FunctionDecl(SourceSpan span, bool local, Var name, AnonFunDecl body)
			: base(span)
		{
			this.Local = local;
			this.FunctionNameExpr = name;
			this.Body = body;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }

        public override void Walk(Walker walker)
        {
            if (FunctionNameExpr!=null)
            {
                FunctionNameExpr.Walk(walker);
            }
            if (Body!=null)
            {
                Body.Walk(walker);
            }

            walker.PostWalk(this);
        }

	}

	// Local variable declaration
	public sealed class LocalVarDecl : Statement
	{
		public readonly Collections.IList<LocalDecl> Decls;
		public readonly Collections.IList<Expression> Init; // null if no init specified

		public LocalVarDecl(SourceSpan span, Collections.IList<LocalDecl> decls, Collections.IList<Expression> init)
			: base(span)
		{
			this.Decls = decls;
			this.Init = init;
		}

        public override object Execute(CodeContext context)
        {
            return base.Execute(context);
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }

        public override void Walk(Walker walker)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }
	}

	// Literal string/number/boolean value; or null for nil
	public sealed class LiteralExpr : Expression
	{
		public readonly object Value;

		public LiteralExpr(SourceSpan span, object value) : base(span)
		{
			this.Value = value;
		}

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
        	if (Value == null)
			{
				cg.EmitNull();
			}
            else if (Value is double)
            {
                cg.Emit(OpCodes.Ldc_R8, (double)Value);
                cg.Emit(OpCodes.Box, typeof(double));
            }
            else if (Value is bool)
            {
				switch ((bool)Value)
				{
					case true:
						cg.Emit(OpCodes.Ldc_I4_1);
						cg.Emit(OpCodes.Box, typeof(bool));
						break;
					case false:
						cg.Emit(OpCodes.Ldc_I4_0);
						cg.Emit(OpCodes.Box, typeof(bool));
						break;
				}
            }
			else if (Value.GetType() == typeof(string))
			{
				cg.EmitString((string)Value);
			}
            else {
                throw new NotImplementedException(); }
        }

        public override void Walk(Walker walker)
        {
            walker.PostWalk(this);
        }
	}

	// Anonymous function declaration
	public sealed class AnonFunDecl : Expression
	{
		public readonly Collections.IList<ParamDecl> Parameters;
		public readonly bool VarArgs;
		public readonly Statement Body;

		public AnonFunDecl(SourceSpan span, Collections.IList<ParamDecl> parameters, bool varArgs, Statement body)
			: base(span)
		{
			this.Parameters = parameters;
			this.VarArgs = varArgs;
			this.Body = body;
		}

        
        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }

        public override void Walk(Walker walker)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }
	}

	// When an expression is parenthesized, sequences are chopped to single
	// values
	public sealed class SingleValueExpr : Var
	{
		public readonly Expression Expression;

		public SingleValueExpr(SourceSpan span, Expression expr) : base(span)
		{
			this.Expression = expr;
		}

        
        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            Expression.Emit(cg);
        }

        public override void Walk(Walker walker)
        {
            if (Expression!=null)
            {
                Expression.Walk(walker);
            }
         
            walker.PostWalk(this);        
        }
	}


	// A variable
	public sealed class NamedVar : Var
	{
		public readonly string Name;
        public readonly CodeBlock Block;
        public VariableReference Reference;
        public Microsoft.Scripting.Internal.Generation.Slot s;
        public bool created = false;

		public NamedVar(SourceSpan span, string name, CodeBlock block) 
            : base(span)
		{
			this.Name = name;
            Reference = new VariableReference(SymbolTable.StringToId(name));
            
 		}

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            if (Reference.Slot==null)
                Reference.CreateSlot(cg);

            BoundExpression exp = new BoundExpression(Reference);
            exp.Emit(cg);
        }

        public void EmitSet(Microsoft.Scripting.Internal.Generation.CodeGen cg, Expression right, Operators op)
        {
            if (Reference.Slot==null)
                Reference.CreateSlot(cg);

            ExpressionStatement exp = new ExpressionStatement(new BoundAssignment(Reference, right, op));
            exp.Emit(cg);
        }

        public override void Walk(Walker walker)
        {
            walker.PostWalk(this);
        }

	}

	// Slot lookup, e.g. exp['Foo'] or exp.Foo
	public sealed class Slot : Var
	{
		public readonly Expression Expression;
		public readonly Expression SlotExpr;

		public Slot(SourceSpan span, Expression expr, Expression slotExpr) : base(span)
		{
			this.Expression = expr;
			this.SlotExpr = slotExpr;
		}
        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            //Expression.Emit(cg);
            //SlotExpr.Emit(cg);
            Collections.List<Expression> exprs = new Collections.List<Expression>();
            exprs.Add(SlotExpr);
            MethodCallExpression call = new MethodCallExpression(typeof(Nua.Runtime.NuaTable).GetMethod("get_Item"), Expression, exprs);
            call.Emit(cg);
        }

        public override void Walk(Walker walker)
        {
            if (Expression!=null)
            {
                Expression.Walk(walker);
            }
            if (SlotExpr!=null)
            {
                SlotExpr.Walk(walker);
            }

            walker.PostWalk(this);
        }

	}

	public sealed class TableCtor : Expression
	{
		public readonly Collections.IList<FieldDecl> Fields;

		public TableCtor(SourceSpan span, Collections.IList<FieldDecl> fields)
			: base(span)
		{
			this.Fields = fields;
		}
        
        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            cg.EmitNew(typeof(Nua.Runtime.NuaTable).GetConstructor(new Type[] {}));
        }

        public override void Walk(Walker walker)
        {
            if (walker.Walk(this))
            {
                if (Fields!=null)
                    foreach (FieldDecl decl in Fields)
                        decl.Walk(walker);
                   
            }
            walker.PostWalk(this);
        }
	}

	// Field declaration in a table constructor
	public sealed class FieldDecl : Expression
	{
		public readonly SourceSpan Span;
		public readonly Expression SlotExpr; // initially, may be null if not assigned an ordinal
		public readonly Expression InitExpr;

		public FieldDecl(SourceSpan span, Expression slotExpr, Expression initExpr)
		{
			this.Span = span;
			this.SlotExpr = slotExpr;
			this.InitExpr = initExpr;
		}

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override void Walk(Walker walker)
        {
            if (walker.Walk(this))
            {
                if (SlotExpr!=null)
                    SlotExpr.Walk(walker);
                if (InitExpr!=null)
                    InitExpr.Walk(walker);
            }
            walker.PostWalk(this);
        }
	}


	// Expression with two operands
	public sealed class BinExpr : Expression
    {
        public readonly Operators Op;
        public readonly Expression Right;
        public readonly Expression Left;

		public BinExpr(SourceSpan span, Expression left, Operators op, Expression right)
			: base(span)
		{
            Left = left;
            Right = right;
            Op = op;
		}

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            ActionExpression exp = new ActionExpression(Microsoft.Scripting.Actions.DoOperationAction.Make(Op), new Expression[] { Left, Right }, Span);
            exp.Emit(cg);
        }

        public override void Walk(Walker walker)
        {            
            if (walker.Walk(this))
            {
                if (Left!=null)
                    Left.Walk(walker);
                if (Right!=null)
                    Right.Walk(walker);
            }
            walker.PostWalk(this);
        }

      
	}

    // An l-valued expression
	public abstract class Var : Expression
	{
		public Var(SourceSpan span) : base(span) { }

		
	}

	// A local variable or parameter declaration
	public abstract class VarDecl
	{
		public readonly string Name;
		public readonly SourceSpan Span;

		public VarDecl(SourceSpan span, string name)
		{
			this.Name = name;
			this.Span = span;
		}
	}

	// Local variable declaration
	public sealed class LocalDecl : VarDecl
	{
		public LocalDecl(SourceSpan span, string name) : base(span, name) { }

        public VariableReference Create(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            VariableReference local = new VariableReference(SymbolTable.StringToId(this.Name));
            local.CreateSlot(cg);

            return local;
            
        }
	}

	// Parameter declaration
	public sealed class ParamDecl : Var
	{
        public readonly string Name;

		public ParamDecl(SourceSpan span, string name) 
            : base(span) 
        {
            this.Name = name;
        }

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }

        public override void Walk(Walker walker)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }
	}

    // Expression with one operand
	public sealed class UnExpr : Expression
	{
		public readonly UnOp Op;
		public readonly Expression Expr;

		public UnExpr(SourceSpan span, UnOp op, Expression expr) : base(span)
		{
			this.Op = op;
			this.Expr = expr;
		}

        public override void Emit(Microsoft.Scripting.Internal.Generation.CodeGen cg)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }

        public override void Walk(Walker walker)
        {
            throw new System.Exception("The method or operation is not implemented.");
        }
	}

	// Operators with one operand
	public enum UnOp : byte
	{
		Not,
		UMinus
	}
}
